using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;
using Sedna.Core.AppConfig;
using Sedna.Domain;
using Sedna.Core.Security;
using Iesi.Collections;
using log4net;
using Spring.Util;

namespace Sedna.Core.Security
{
    public class UserMapper
    {
        private static Hashtable users=new Hashtable();
        private static Hashtable usedItems = new Hashtable();
                     
        private static long sessionID = 100;
        private static DateTime lastGarbageCollection = DateTime.Now;
        private static int timeout = 0;
        private static ILog log = LogManager.GetLogger(typeof(UserMapper));
        
        private static void GarbageCollection()
        {            
            if (timeout == 0)
                timeout = AppConfigurationHolder.LoginGarbageCollectionTimeout();

            lock (typeof(UserMapper))
            {
                TimeSpan lastInterval = (DateTime.Now - lastGarbageCollection);
                
                if (lastInterval.TotalMinutes >= timeout)
                {
                    log.Debug("USERMAPPER: Garbage collection process");
                    lastGarbageCollection = DateTime.Now;
                    Hashtable newMap = new Hashtable(usedItems.Count);
                    foreach (DictionaryEntry entry in usedItems)
                    {
                        UsedItem usedItem = (UsedItem) entry.Value;
                        if (!usedItem.IsExpired)
                            newMap.Add(entry.Key.ToString(), users[entry.Key.ToString()]);
                    }
                    users = newMap;

                    usedItems = new Hashtable();
                }
            }
        }
        
        private static string GenerateSessionId()
        {
            long id;
            lock (typeof(UserMapper)) id = sessionID++;
            
            StringBuilder builder = new StringBuilder(id.ToString());
            Random rnd=new Random();

            for (int i = 1; i <= 15; i++) builder.Append((char)(rnd.Next(0x41,0x7a)));
            
            return users[builder.ToString()] == null ? builder.ToString() : GenerateSessionId();
        }

        public static void StoreCurrentState()
        {
            lock (typeof(UserMapper))
            {
                SednaWorkPrincipal wp = Thread.CurrentPrincipal as SednaWorkPrincipal;
                if (wp.Culture==null)
                    wp.Culture = Thread.CurrentThread.CurrentCulture;
                string sessId = GenerateSessionId();
                log.Debug(string.Format("USERMAPPER: granted SID {0} to user {1}",sessId,wp.Identity.Name));
                users[sessId.ToString()] = wp;
                HttpCookie cookie = new HttpCookie("SEDNA_SessionId", sessId);
                HttpContext.Current.Request.Cookies.Clear();
                HttpContext.Current.Request.Cookies.Add(cookie);
                HttpContext.Current.Response.Cookies.Clear();
                HttpContext.Current.Response.Cookies.Add(cookie);
            }
        }
        
        public static void UpdateState()
        {

            if (HttpContext.Current.Request.Headers["X-MicrosoftAjax"] != null) return; //todo: workaround for AJAX calls. Cannot set cookies
            if (HttpContext.Current.Request.Cookies["SEDNA_SessionId"]!=null)
            {
                string sessionId = HttpContext.Current.Request.Cookies["SEDNA_SessionId"].Value;

                HttpCookie cookie = new HttpCookie("SEDNA_SessionId", sessionId);
                HttpContext.Current.Request.Cookies.Clear();
                HttpContext.Current.Response.Cookies.Clear();
                HttpContext.Current.Request.Cookies.Add(cookie);
                HttpContext.Current.Response.Cookies.Add(cookie);
            }
        }
        
        public static SednaWorkPrincipal RestoreCurrentState()
        {
            string sessionId = null;
            if (HttpContext.Current.Request.Cookies["SEDNA_SessionId"] != null)
                sessionId = HttpContext.Current.Request.Cookies["SEDNA_SessionId"].Value;            
            
            log.Debug(string.Format("USERMAPPER: State Restoring request. SID: {0}", sessionId ?? "EMPTY"));
            if (sessionId != null)
                log.Debug(string.Format("USERMAPPER: Cached object: {0}", (users[sessionId] == null) ? "NOT FOUND" : "FOUND"));
            
            if (sessionId == null || users[sessionId] == null) return null;

            lock (typeof(UserMapper))
            {
                UsedItem usedItem = null;
                if (usedItems[sessionId] == null)
                {
                    usedItems[sessionId] = new UsedItem(sessionId);
                }
                usedItem = (UsedItem)usedItems[sessionId];
                usedItem.Update();

                GarbageCollection();
            }

            log.Debug(string.Format("USERMAPPER: Restoring state. SID: {0}. User: {1}", sessionId,
                users[sessionId] != null ? ((SednaWorkPrincipal)users[sessionId]).Identity.Name : "expired"));

            return (SednaWorkPrincipal)users[sessionId];                      
        }   
        
        public static void RemoveUser()
        {
            lock (typeof(UserMapper))
            {
                string sessionId = null;
                if (HttpContext.Current.Request.Cookies["SEDNA_SessionId"] != null)
                {
                    sessionId = HttpContext.Current.Request.Cookies["SEDNA_SessionId"].Value;
                    users.Remove(sessionId);
                }
            }
        }
        
        private static TimeSpan GetLoginTimeout()
        {
            return new TimeSpan(0, 0, AppConfigurationHolder.LoginTimeout(), 0);
        }
        
        private class UsedItem
        {
            private string sessionId;
            private DateTime requestDate = DateTime.Now;

            public UsedItem(string sessionId)
            {
                this.sessionId = sessionId;
            }

            #region Equals/GetHashCode
            
            public override int GetHashCode()
            {
                return StringUtils.IsNullOrEmpty(sessionId) ? base.GetHashCode() : sessionId.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                if (!(obj is UsedItem))
                    return false;
                
                if (StringUtils.IsNullOrEmpty(sessionId))
                    return base.Equals(obj);
                return ((UsedItem) obj).sessionId == sessionId;
            }

            #endregion
            
            public bool IsExpired
            {
                get 
                {
                    return (DateTime.Now - requestDate) > GetLoginTimeout();
                }
            }

            public bool Update()
            {
                log.Debug("Update call");
                if (IsExpired)
                    return true;

                requestDate = DateTime.Now;
                return false;
            }
        }
        
    }
}
